﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
using Snake.Core;
using Snake.Database;
using Snake.Mapping;
using Snake.SQL.SQLNode;
using Snake.Utils;

namespace Snake.SQL
{
    class QueryConverter
    {
        private readonly DbContext _context;
        private readonly OperateEnum _operate;
        public QueryConverter(DbContext context, OperateEnum operate)
        {
            _context = context;
            _operate = operate;
        }

        public ISQLNode Visit(Expression expr)
        {
            return Visit(null, expr);
        }

        private ISQLNode Visit(ISQLNode parent, Expression expr)
        {
            if (expr.NodeType == ExpressionType.Constant && expr.Type.IsGenericType(typeof(Queryable<>)))
                return VisitRoot(parent, expr);

            if (expr.NodeType == ExpressionType.Call)
            {
                var call = (MethodCallExpression)expr;
                switch (call.Method.Name)
                {
                    case "Where":
                        return VisitWhere(Visit(parent, call.Arguments[0]), call);
                    case "OrderBy":
                        return VisitOrderBy(Visit(parent, call.Arguments[0]), call);
                    case "ThenBy":
                        return VisitThenBy(Visit(parent, call.Arguments[0]), call);
                    case "OrderByDescending":
                        return VisitOrderByDescending(Visit(parent, call.Arguments[0]), call);
                    case "ThenByDescending":
                        return VisitThenByDescending(Visit(parent, call.Arguments[0]), call);
                    case "Take":
                        return VisitTake(Visit(parent, call.Arguments[0]), call);
                    case "Skip":
                        return VisitSkip(Visit(parent, call.Arguments[0]), call);
                }
            }

            throw new NotSupportedException("Not supported expression: " + expr);
        }

        private ISQLNode VisitWhere(ISQLNode parent, MethodCallExpression expr)
        {
            var _dbOperate = (DbOperate)parent;
            var whereConverter = new WhereConverter(_dbOperate, _context);
            var whereExpr = whereConverter.Visit(ExpressionHelper.GetLambda(expr.Arguments[1]));

            _dbOperate.Where = whereExpr;

            return _dbOperate;
        }

        private ISQLNode VisitSkip(ISQLNode parent, MethodCallExpression expr)
        {
            var _dbOperate = (Select)parent;
            var arg = expr.Arguments[1];

            if (arg.NodeType == ExpressionType.Constant)
            {
                return new SelectPaging(_context.NextAlias(), _dbOperate, new ConstantProvider(_context, (ConstantExpression)arg));
            }

            if (arg.NodeType == ExpressionType.Parameter)
            {
                return new SelectPaging(_context.NextAlias(), _dbOperate, new ParameterProvider(_context, (ParameterExpression)arg));
            }

            throw new NotSupportedException();
        }

        private ISQLNode VisitTake(ISQLNode parent, MethodCallExpression expr)
        {
            var _dbOperate = (DbOperate)parent;
            var arg = expr.Arguments[1];

            if (arg.NodeType == ExpressionType.Parameter)
            {
                _dbOperate.Top = new Top(new ParameterProvider(_context, (ParameterExpression)arg));
            }
            else if (arg.NodeType == ExpressionType.Constant)
            {
                _dbOperate.Top = new Top(new ConstantProvider(_context, (ConstantExpression)arg));
            }
            else
            {
                throw new NotSupportedException();
            }

            return _dbOperate;
        }

        private ISQLNode VisitThenBy(ISQLNode parent, MethodCallExpression expr)
        {
            return VisitOrderBy(parent, expr, true);
        }

        private ISQLNode VisitOrderByDescending(ISQLNode parent, MethodCallExpression expr)
        {
            return VisitOrderBy(parent, expr, false);
        }

        private ISQLNode VisitThenByDescending(ISQLNode parent, MethodCallExpression expr)
        {
            return VisitOrderBy(parent, expr, false);
        }

        private ISQLNode VisitOrderBy(ISQLNode parent, MethodCallExpression expr)
        {
            return VisitOrderBy(parent, expr, true);
        }

        private ISQLNode VisitOrderBy(ISQLNode parent, MethodCallExpression expr, bool ascending)
        {
            var select = (Select)parent;

            BracketedName alias;
            PropertyMap map;

            PropertyInfo[] path = ExpressionHelper.GetPropertyPath(ExpressionHelper.GetLambda(expr.Arguments[1]).Body);
            select.VisitPath(_context, path, out alias, out map);

            var orderBy = select.OrderBy ?? (select.OrderBy = new OrderBy());
            orderBy.AddItem(alias, map, ascending);

            return select;
        }

        private ISQLNode VisitRoot(ISQLNode parent, Expression expr)
        {
            //DbOperate operate;
            switch (_operate)
            {
                case OperateEnum.Select:
                   return new Select(_context);
                    break;
                case OperateEnum.Delete:
                    return new Delete(_context);
                    break;
                default:
                    return new Select(_context);
                    break;
            }
           // return operate;
        }
    }
}
